/////////////////////////////////////////////////////////////////////////////////////////////////////
// 
// NHP协议的企业微信H5程序/小程序JS SDK
// 版本号：v1.4 (发布于2024-04-02）
// 关于NHP协议的定义，请参见中国计算机学会CCF的《零信任网络隐身协议规范》
// 反馈意见请发送至： zsec@z-sec.net，感谢您的参与支持！
//
/////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////
// 常量定义
////////////////////////////////////////////////////////////////////////////

// 全局开关
var DEBUG_MODE = 0; // 是否开启Debug模式，发布时应该为0

// 错误码定义
const ERR_SUCCESS = 0;        // 成功
const ERR_EXIT    = 1;        // 立即退出
const ERR_HTTPREQ = -1;       // 对服务器的HTTP请求失败为-1
const ERR_INVALIDPARAM = -2;  // 错误的函数参数
const MSG_INVALIDPARAM = '[NHP] Invalid Parameters'; //ERR_INVALIDPARAM对应的错误消息
const ERR_USER_DENIED = -3;
const MSG_USER_DENIED = "[NHP] User Denied!";
const ERR_INVALID_SDK = -4;
const MSG_INVALID_SDK = "[NHP] Invalid h5sdk!";
const ERR_EMPTY_CODE = -5; 
const MSG_EMPTY_CODE = "[NHP] AuthCode is Empty!";

// Header定义
const NHP_HEADER = 'X-NHP-Token';    // NHP Token
// NHP SDK版本
const NHP_VERSION = "1.4"; // NHP SDK Version
// Debug 输出的元素
const NHP_DEBUG_ELEMENT = "nhp-log";

////////////////////////////////////////////////////////////////////////////
// 内部函数
////////////////////////////////////////////////////////////////////////////

// Print debug log
function debugLog(msg) {
  if (!DEBUG_MODE) { // 调试模式下，才打印日志
    return;
  }
  console.info(msg);
  // 移动设备上，输出到某个DIV下面
  if (window.document) {
    const logDiv = document.getElementById(NHP_DEBUG_ELEMENT);
    if (logDiv) {
      if (typeof msg === 'object') {
        logDiv.innerHTML = JSON.stringify(msg);
      } else {
        logDiv.innerHTML = msg;
      }
    }
  }
}

// 兼容老版本SDK的获取authCode函数
function qwCallRequestAuthCode(resId, callback) {
    window.tt.requestAuthCode({
        // 网页应用 App ID
        appId: resId,
        success: (info) => {
        // 用户免登录后返回预授权码
        callback({
            errCode: ERR_SUCCESS,
            code: info.code
        });
        },
        fail: (error) => {
        // 免登失败，返回相应的errno和errString
        callback({
            errCode: error.errno,
            errMsg: errString
        });
        },
    });
} // function qwCallRequestAuthCode

  // 新版本SDK获取AuthCode
function qwGetAuthCode(resId, callback) {
    const LJ_TOKEN_KEY = 'lk_token';

    let lj_tokenString = "";
    if (window.Cookies) {
      lj_tokenString = Cookies.get(LJ_TOKEN_KEY);
    }
    if (lj_tokenString.length > 0) {
      debugLog("接入方前端[免登处理]第① 步: 用户已登录，请求后端验证...");
      callback({
        errCode: ERR_SUCCESS, //成功
        code: ""  // 让后端去验证        
      });
    } else {
      if (!window.h5sdk) {
        debugLog(MSG_INVALID_SDK);
        callback({
          errCode: ERR_INVALID_SDK,
          errMsg: MSG_INVALID_SDK
        });
        return;
      }
      debugLog("接入方前端[免登处理]第① 步: 依据App ID调用JSAPI tt.requestAccess 请求免登授权码");
      //依据App ID调用JSAPI tt.requestAccess 请求临时授权码code
      window.h5sdk.ready(() => {
        debugLog("window.h5sdk.ready");
        if (window.tt.requestAccess) {
          window.tt.requestAccess({
            // 网页应用 App ID
            appID: resId,
            scopeList: [],
            success: (res) => {
              // 用户授权后返回预授权码
              if (res.code.length <= 0) {
                debugLog(MSG_EMPTY_CODE);
                callback({
                  errCode: ERR_EMPTY_CODE,
                  errMsg: MSG_EMPTY_CODE
                });
              } else {
                callback({
                  errCode: ERR_SUCCESS, //成功
                  code: res.code
                });
              }
            },
            fail: (error) => {
              // 需要额外根据errno判断是否为 客户端不支持requestAccess导致的失败
              const { errno, errString } = error;
              if (errno === 103) {
                // 客户端版本过低，不支持requestAccess，需要改为调用requestAuthCode
                qwCallRequestAuthCode(resId, callback);
              } else {
                // 用户拒绝授权或者授权失败
                callback({
                  errCode: ERR_USER_DENIED,
                  errMsg: MSG_USER_DENIED
                });
              }
            },
          });
        } else { // JSSDK版本过低，不支持requestAccess，需要改为调用requestAuthCode
          qwCallRequestAuthCode(resId, callback);
        }
      });
    } //if (lj_tokenString.length > 0) {
} // function qwGetAuthCode

////////////////////////////////////////////////////////////////////////////
// 内部函数，用于敲门
////////////////////////////////////////////////////////////////////////////
function nhpKnockDoor(nhpServer, aspId, resId, callback) {
    // 打印调试信息
    debugLog(
        "[NHP] nhpKnockDoor: " + 
        "; nhpServer= " + nhpServer +
        "; aspId= " + aspId +
        "; resId= " + resId
    );

    // 参数校验
    if (!nhpServer || !aspId || !resId || !callback) {
        debugLog(MSG_INVALIDPARAM);
        callback({
            errCode: ERR_INVALIDPARAM, // 输入参数错误，程序中断
            errMsg:  MSG_INVALIDPARAM
        });
        return;
    }
    // 调用企业微信的authCode获取函数
    qwGetAuthCode(resId, function(codeResult) {
        if (codeResult.errCode == ERR_SUCCESS) {
            // NHP服务器请求URL
            const nhpURL = "https://" + nhpServer + 
            "/plugins/" + encodeURIComponent(aspId) +
            "?resid=" + encodeURIComponent(resId) +
            "&code=" + encodeURIComponent(codeResult.code) +
            "&action=h5valid"; 
        
            debugLog("[NHP] fetch: " + nhpURL);

            // 向NHP服务器发起AJAX请求
            fetch(nhpURL, {
                method: 'GET',
                headers: {
                    'X-NHP-Ver': NHP_VERSION
                },
            })
            .then((response) => {
                debugLog(response);
                if (!response.ok) {
                    throw new Error(`[NHP] HTTP error: ${response.status}`);
                }
                return response.json();
            })
            .then((json) => {
                // 对NHP服务器的HTTP请求成功，返回给调用者
                callback(json);
            })
            .catch((err) => {
                // 对NHP服务器的HTTP请求失败，需要提示调用者
                callback({
                    errCode: ERR_HTTPREQ, // 对NHP服务器的HTTP请求失败为-2；
                    errMsg: err.message
                });   
            }); 
        } else {
            callback(codeResult);
        }// if (codeResult.errCode == ERR_SUCCESS) {
    }); //qwGetAuthCode(resId, function(codeResult) {
} //function nhpKnockDoor

/**
 * debug模式开关函数
 *
 * @param {number} debugMode 0：关闭debug模式；1：打开debug模式；
 *
 */
function debug(debugMode) {
    DEBUG_MODE = debugMode;
} // function debug

/**
 * NHP协议登录并且敲门
 *
 * @param {string} nhpServer NHP服务器地址，可以为域名或者IP地址，例如"nhp.opennhp.cn"。
 * @param {string} aspId 身份验证授权服务提供商ASP的ID：企业微信是"qiwei"。
 * @param {string} resId 资源ID字符串，一般为企业微信小程序/H5程序的AppId，如"wwdd36ce2d93b5b945"。
 * @param {function} callback 回调函数，callback(msg)，msg为NHP服务器返回的消息。
 *
 */
function login(nhpServer, aspId, resId, callback) {
  debugLog("[NHP] login: " + 
    "; nhpServer= " + nhpServer +
    "; aspId= " + aspId +
    "; resId= " + resId
  );
  // 直接调用knockdoor函数
  nhpKnockDoor(nhpServer, aspId, resId, callback);
}

/**
 * NHP协议开启敲门函数，调用open函数将开启以interval间隔为时长的连续敲门。
 *
 * @param {string} userToken 企业微信客户端的用户user_access_token，用户向企业微信服务器验证用户身份。
 * @param {number} interval 敲门间隔时长，以毫秒ms为单位，例如20*1000 表示20秒。
 * @param {string} nhpServer NHP服务器地址，可以为域名或者IP地址，例如"nhp.opennhp.cn"。
 * @param {string} aspId 身份验证授权服务提供商ASP的ID：企业微信是"qiwei"。
 * @param {string} resId 资源ID字符串，一般为企业微信小程序/H5程序的AppId，如"wwdd36ce2d93b5b945"。
 * @param {function} callback 回调函数，callback(msg)，msg为NHP服务器返回的消息。
 *
 * @return {number} NHP的敲门计时器ID，即timerID，可以通过调用close(timerID)来关闭敲门。
 */
function open(interval, nhpServer, aspId, resId, callback) {
    debugLog(
        "[NHP] open: "  + 
        "; interval=" + interval + 
        "; nhpServer=" + nhpServer + 
        "; aspId=" + aspId + 
        "; resId=" + resId
    );
  
    // 马上执行一次敲门函数
    nhpKnockDoor(
        nhpServer,    // NHP 服务器地址，例如"nhp.opennhp.cn"
        aspId,        // 身份验证授权服务提供商ASP的ID，这里为”qiwei"
        resId,        // 资源ID字符串，一般为企业微信小程序/H5的AppId，如"wwdd36ce2d93b5b945"
        callback      // 回调函数，callback(msg)，msg为NHP服务器返回的消息
    );

    // 每隔interval秒执行一次 敲门函数
    const timerID = setInterval(
        function() {
            nhpKnockDoor(nhpServer, aspId, resId, callback); // 敲门函数
        },
        interval      // 敲门间隔
    );
    return timerID;
} // function open

/**
 * NHP协议关闭敲门函数。
 *
 * @param {number} timerID NHP的敲门计时器ID。
 */
function close(timerID) {
    debugLog("[NHP] close: timerID=" + timerID); 
    if (timerID) {
        clearInterval(timerID);
    }
} //function close

/**
 * NHP协议开启敲门函数open的Promise实现。
 *
 * @param {string} userToken 企业微信客户端的用户user_access_token，用户向企业微信服务器验证用户身份。
 * @param {number} interval 敲门间隔时长，以毫秒ms为单位，例如20*1000 表示20秒。
 * @param {string} nhpServer NHP服务器地址，可以为域名或者IP地址，例如"nhp.opennhp.cn"。
 * @param {string} aspId 身份验证授权服务提供商ASP的ID：企业微信是"qiwei"。
 * @param {string} resId 资源ID字符串，一般为企业微信小程序/H5的AppId，如"wwdd36ce2d93b5b945"
 * @return {Promise} 开门结果的Promise对象。
 */
function openPromise(userToken, interval, nhpServer, aspId, resId) {
    debugLog(
        "[NHP] openPromise: userToken=" + appToken + 
        "; interval=" + interval + 
        "; nhpServer=" + nhpServer + 
        "; aspId=" + aspId + 
        "; resId=" + resId
      );
    
      return new Promise((resolve, reject) => {
        open(userToken, interval, nhpServer, aspId, resId, function(msg) {
          if (msg.errCode == ERR_SUCCESS) {
            resolve(msg);
          } else {
            reject(msg);
          }
        });
      });    
}

////////////////////////////////////////////////////////////////////////////
// 本JS模块对外暴露的函数和常量
////////////////////////////////////////////////////////////////////////////
export default {
    login: login,   // 登录
    open: open,     // 开启敲门
    openPromise : openPromise, // 开启敲门的Promise实现
    close: close,   // 关闭敲门
    debug: debug,   // 开启/关闭Debug模式
    httpHeader: NHP_HEADER, // NHP HTTP Header name
    ERR_CODE: {     // 错误码常量
        success: ERR_SUCCESS,
        exit: ERR_EXIT,
        httpReq: ERR_HTTPREQ,
        invalidParam: ERR_INVALIDPARAM,
        userDenied: ERR_USER_DENIED,
        invalidSDK:  ERR_INVALID_SDK,
        emptyCode: ERR_EMPTY_CODE
    }
};
